import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.client.utils.HttpClientUtils;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.concurrent.NotThreadSafe;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.Serializable;
import java.math.BigDecimal;
import java.net.URI;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @Description
 * @auther taoruicheng
 * @create 2019-05-15 12:52
 */
public class HttpUtils {
    private final static Logger logger =  LoggerFactory.getLogger(HttpUtils.class);
    /*
     * 最大链接数
     */
    private final static int MAX_TOTAL_CONS =500;
    /*
     * 每个路由最大连接数
     */
    private final static int MAX_ROUTE_CONS = 500;
    /*
     * 链接超时时间
     */
    private final static int CON_TIMEOUT = 20000;
    /*
     * 读取超时时间
     */
    private final static int READ_TIMEOUT = 2*60*1000;
    private final static RequestConfig config = RequestConfig.custom()
            .setConnectTimeout(CON_TIMEOUT)
            .setSocketTimeout(READ_TIMEOUT).build();
    private static Registry<ConnectionSocketFactory> socketFactoryRegistry ;

    static {
        SSLContext sslcontext ;
        try{
            //采用绕过验证的方式处理https请求
            sslcontext = createIgnoreVerifySSL();
        }catch (Exception e){
            throw new RuntimeException(e);
        }
        SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslcontext,new String[]{"TLSv1.2","TLSv1","SSLv3","SSLv2Hello"},null, NoopHostnameVerifier.INSTANCE);
        //设置协议http和https对应的处理socket链接工厂的对象
        socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.INSTANCE)
                .register("https", sslConnectionSocketFactory)
                .build();

    }
    /**
     * 绕过验证
     *
     * @return
     * @throws NoSuchAlgorithmException
     * @throws KeyManagementException
     */
    private static SSLContext createIgnoreVerifySSL() throws NoSuchAlgorithmException, KeyManagementException {
        SSLContext sc = SSLContext.getInstance("SSLv3");

        // 实现一个X509TrustManager接口，用于绕过验证，不用修改里面的方法
        X509TrustManager trustManager = new X509TrustManager() {
            @Override
            public void checkClientTrusted(
                    java.security.cert.X509Certificate[] paramArrayOfX509Certificate,
                    String paramString) throws CertificateException {
            }

            @Override
            public void checkServerTrusted(
                    java.security.cert.X509Certificate[] paramArrayOfX509Certificate,
                    String paramString) throws CertificateException {
            }

            @Override
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        };

        sc.init(null, new TrustManager[]{trustManager}, null);
        return sc;
    }

    /**
     * 得到HttpClient
     * @return
     * @throws Exception
     */
    private static CloseableHttpClient getHttpClient() throws Exception {
        PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
        connManager.setDefaultMaxPerRoute(MAX_ROUTE_CONS);
        connManager.setMaxTotal(MAX_TOTAL_CONS);
        return HttpClients.custom()
                .setDefaultRequestConfig(config)
                .setConnectionManager(connManager)
                .build();
    }

    public static HttpResponseBody doGet(String url,Map<String,Object> param,Header header) throws Exception{
        CloseableHttpResponse response = null;
        CloseableHttpClient httpClient = null;
        StringBuilder sb = new StringBuilder();
        appendGetUrl(sb,url,param);
        try {
            HttpGet get = new HttpGet(sb.toString());
            get.addHeader(header);
            httpClient = getHttpClient();
            response = httpClient.execute(get);
            HttpEntity entity = response.getEntity();
            return new HttpResponseBody(response.getStatusLine().getStatusCode(),EntityUtils.toString(entity, "utf-8"));
        } catch (Exception e) {
            logger.error("url:{}.doGet has error ",url,e);
            throw e;
        }finally {
            HttpClientUtils.closeQuietly(response);
            HttpClientUtils.closeQuietly(httpClient);
        }
    }

    /**
     * 以普通的方式post提交，参数放在params中
     * @param url
     * @param params
     * @param header
     * @return
     */
    public static HttpResponseBody doPost(String url, Map<String,Object> params, Header header) {
        CloseableHttpClient httpclient = null;
        CloseableHttpResponse response = null;
        try {
            httpclient = getHttpClient();
            HttpPost httpPost = new HttpPost(url);
            List<NameValuePair> pairs = new ArrayList<NameValuePair>();
            for (String key: params.keySet()){
                Object o = params.get(key);
                pairs.add(new BasicNameValuePair(key,o==null?null:o.toString()));
            }
            UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(pairs);
            httpPost.setEntity(urlEncodedFormEntity);
            httpPost.addHeader(header);
            response = httpclient.execute(httpPost);
            HttpEntity entity = response.getEntity();
            return new HttpResponseBody(response.getStatusLine().getStatusCode(),EntityUtils.toString(entity, "utf-8"));
        } catch (Exception e) {
            logger.error("doPost has error ",e);
            return null;
        }finally {
            HttpClientUtils.closeQuietly(response);
            HttpClientUtils.closeQuietly(httpclient);
        }
    }
    /**
     * 用于发送参数为JSON字符串的POST请求
     * @param url 请求路径
     * @param jsonStr Json字符串参数
     * @param header token认证 可为空
     * @return
     */
    public static HttpResponseBody doPostByJson(String url, String jsonStr, Header header) {
        CloseableHttpClient httpclient = null;
        CloseableHttpResponse response = null;
        try {
            httpclient = getHttpClient();
            HttpPost httpPost = new HttpPost(url);
            httpPost.addHeader(HTTP.CONTENT_TYPE, "application/json");
            httpPost.setHeader(HTTP.CONTENT_ENCODING,"UTF-8");
            StringEntity se = new StringEntity(jsonStr, ContentType.APPLICATION_JSON);
            httpPost.setEntity(se);
            httpPost.addHeader(header);
            response = httpclient.execute(httpPost);
            HttpEntity entity = response.getEntity();
            return new HttpResponseBody(response.getStatusLine().getStatusCode(), EntityUtils.toString(entity, "UTF-8"));
        } catch (Exception e) {
            logger.error("doPostByJson has error ",e);
            return null;
        }finally {
            HttpClientUtils.closeQuietly(response);
            HttpClientUtils.closeQuietly(httpclient);
        }
    }
    /**
     * HttpDelete 请求
     * @param url 请求路径
     * @param header tokeng认证  可为null
     * @return
     */
    public static HttpResponseBody doDelete(String url, Header header) {
        CloseableHttpClient httpclient = null;
        CloseableHttpResponse response = null;
        try {
            httpclient = getHttpClient();
            HttpDelete delete =  new HttpDelete(url);
            delete.addHeader(header);
            response = httpclient.execute(delete);
            HttpEntity entity = response.getEntity();
            return new HttpResponseBody(response.getStatusLine().getStatusCode(),EntityUtils.toString(entity, "utf-8"));
        } catch (Exception e) {
            logger.error("doDelete has error ",e);
            return null;
        }finally {
            HttpClientUtils.closeQuietly(response);
            HttpClientUtils.closeQuietly(httpclient);
        }
    }
    /**
     * HttpDelete 请求
     * @param url 请求路径
     * @param jsonStr 请求参数  JSON格式字符串
     * @param header tokeng认证  可为null
     * @return
     */
    public static HttpResponseBody doDeleteByJson(String url, String jsonStr, Header header) {
        CloseableHttpClient httpclient = null;
        CloseableHttpResponse response = null;
        try {
            httpclient = getHttpClient();
            HttpDeleteWithBody delete = new HttpDeleteWithBody(url);
            delete.addHeader(HTTP.CONTENT_TYPE, "application/json");
            StringEntity se = new StringEntity(jsonStr);
            delete.setEntity(se);
            delete.addHeader(header);
            response = httpclient.execute(delete);
            HttpEntity entity = response.getEntity();
            return new HttpResponseBody(response.getStatusLine().getStatusCode(),EntityUtils.toString(entity, "utf-8"));
        } catch (Exception e) {
            logger.error("doDeleteByJson has error ",e);
            return null;
        }finally {
            HttpClientUtils.closeQuietly(response);
            HttpClientUtils.closeQuietly(httpclient);
        }
    }

    /**
     * 下载文件到本地，报错的话，则下载失败。
     * @param url
     * @param toLocalFile
     * @param fileCache
     * @throws Exception
     */
    public static void downloadFileToLocal(String url, File toLocalFile,int fileCache) throws Exception{
        CloseableHttpResponse response = null;
        CloseableHttpClient httpClient = null;
        long start = System.currentTimeMillis();
        try {
            HttpGet get = new HttpGet(url);
            httpClient = getHttpClient();
            response = httpClient.execute(get);
            InputStream inStream = response.getEntity().getContent();

            FileOutputStream fileOutputStream = new FileOutputStream(toLocalFile);
            byte[] buffer=new byte[fileCache];
            int ch = 0;
            while ((ch = inStream.read(buffer)) != -1) {
                fileOutputStream.write(buffer,0,ch);
            }
            inStream.close();
            fileOutputStream.flush();
            fileOutputStream.close();
        } catch (Exception e) {
            logger.error("url:{}.toLocalFile:{},doGet has error.",url,toLocalFile.getAbsolutePath(),e);
            throw e;
        }finally {
            HttpClientUtils.closeQuietly(response);
            HttpClientUtils.closeQuietly(httpClient);
            logger.info("文件大小:{},耗时{}ms,下载文件：{} 到本地:{}，",getPrintSize(toLocalFile.length()),System.currentTimeMillis() - start,url,toLocalFile.getAbsolutePath());
        }
    }
    private static String getPrintSize(long size) {
        // 如果字节数少于1024，则直接以B为单位，否则先除于1024，后3位因太少无意义
        double value = (double) size;
        if (value < 1024) {
            return String.valueOf(value) + "B";
        } else {
            value = new BigDecimal(value / 1024).setScale(2, BigDecimal.ROUND_DOWN).doubleValue();
        }
        // 如果原字节数除于1024之后，少于1024，则可以直接以KB作为单位
        // 因为还没有到达要使用另一个单位的时候
        // 接下去以此类推
        if (value < 1024) {
            return String.valueOf(value) + "KB";
        } else {
            value = new BigDecimal(value / 1024).setScale(2, BigDecimal.ROUND_DOWN).doubleValue();
        }
        if (value < 1024) {
            return String.valueOf(value) + "MB";
        } else {
            // 否则如果要以GB为单位的，先除于1024再作同样的处理
            value = new BigDecimal(value / 1024).setScale(2, BigDecimal.ROUND_DOWN).doubleValue();
            return String.valueOf(value) + "GB";
        }
    }


    public static void appendGetUrl(StringBuilder sb ,String url,Map<String,Object> param){
        sb.append(url);
        if(param!=null && param.size()>0){
            sb.append("?");
            int count =0;
            for (String key:param.keySet()){
                count++;
                if(count==param.size()){
                    sb.append(key).append("=").append(param.get(key));
                }else {
                    sb.append(key).append("=").append(param.get(key)).append("&");
                }
            }
        }
    }

    public static class HttpResponseBody implements Serializable {
        private static final long serialVersionUID = 1L;
        protected Integer statusCode;
        protected String body;
        public HttpResponseBody() {
        }

        public HttpResponseBody(Integer statusCode, String body) {
            this.statusCode = statusCode;
            this.body = body;
        }

        public Integer getStatusCode() {
            return statusCode;
        }

        public void setStatusCode(Integer statusCode) {
            this.statusCode = statusCode;
        }

        public String getBody() {
            return body;
        }

        public void setBody(String body) {
            this.body = body;
        }

        @Override
        public String toString() {
            return "HttpResponseBody{" +
                    "statusCode=" + statusCode +
                    ", body='" + body + '\'' +
                    '}';
        }

    }
    @NotThreadSafe
    public static class HttpDeleteWithBody extends HttpEntityEnclosingRequestBase {

        public static final String METHOD_NAME = "DELETE";

        @Override
        public String getMethod() {
            return METHOD_NAME;
        }

        public HttpDeleteWithBody(final String uri) {
            super();
            setURI(URI.create(uri));
        }
        public HttpDeleteWithBody(final URI uri) {
            super();
            setURI(uri);
        }
        public HttpDeleteWithBody() { super(); }
    }
}



